package com.tiamosu.databinding.delegate

import androidx.annotation.IdRes
import androidx.annotation.RestrictTo
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.tiamosu.databinding.internal.getRootView
import com.tiamosu.navigation.ext.getLifecycleOwner
import kotlin.reflect.KProperty

/**
 * Create new [ViewBinding] associated with the [Fragment]
 */
fun <F : Fragment, T : ViewBinding> Fragment.lazyDataBinding(
    @IdRes viewBindingRootId: Int = 0,
    onViewDestroyed: (T) -> Unit = {},
): ViewBindingProperty<F, T> {
    return lazyDataBinding({ fragment: F ->
        when (fragment) {
            is DialogFragment -> {
                val contentView = fragment.getRootView(viewBindingRootId)
                checkNotNull(DataBindingUtil.bind(contentView) as? T) { "dataBinding must no be null" }
            }
            else -> {
                val contentView = fragment.requireView()
                checkNotNull(DataBindingUtil.bind(contentView) as? T) { "dataBinding must no be null" }
            }
        }
    }, onViewDestroyed)
}

/**
 * Create new [ViewBinding] associated with the [Fragment]
 */
fun <F : Fragment, T : ViewBinding> Fragment.lazyDataBinding(
    viewBinder: (F) -> T,
    onViewDestroyed: (T) -> Unit = {},
): ViewBindingProperty<F, T> {
    return when (this) {
        is DialogFragment -> dialogFragmentViewBinding(viewBinder, onViewDestroyed)
        else -> fragmentViewBinding(viewBinder, onViewDestroyed)
    }
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun <F : Fragment, T : ViewBinding> fragmentViewBinding(
    viewBinder: (F) -> T,
    onViewDestroyed: (T) -> Unit,
    viewNeedInitialization: Boolean = true
): ViewBindingProperty<F, T> {
    return FragmentViewBindingProperty(viewNeedInitialization, viewBinder, onViewDestroyed)
}

@Suppress("UNCHECKED_CAST")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun <F : Fragment, T : ViewBinding> dialogFragmentViewBinding(
    viewBinder: (F) -> T,
    onViewDestroyed: (T) -> Unit,
    viewNeedInitialization: Boolean = true
): ViewBindingProperty<F, T> {
    return DialogFragmentViewBindingProperty(
        viewNeedInitialization,
        viewBinder,
        onViewDestroyed
    ) as ViewBindingProperty<F, T>
}

private class DialogFragmentViewBindingProperty<in F : DialogFragment, out T : ViewBinding>(
    private val viewNeedInitialization: Boolean,
    viewBinder: (F) -> T,
    onViewDestroyed: (T) -> Unit,
) : LifecycleViewBindingProperty<F, T>(viewBinder, onViewDestroyed) {

    override fun getLifecycleOwner(thisRef: F): LifecycleOwner {
        return when (thisRef.view) {
            null -> thisRef
            else -> thisRef.getLifecycleOwner
        }
    }

    override fun isViewInitialized(thisRef: F): Boolean {
        if (!viewNeedInitialization) {
            return true
        }

        return if (thisRef.showsDialog) {
            thisRef.dialog != null
        } else {
            thisRef.view != null
        }
    }
}

private class FragmentViewBindingProperty<in F : Fragment, out T : ViewBinding>(
    private val viewNeedInitialization: Boolean,
    viewBinder: (F) -> T,
    onViewDestroyed: (T) -> Unit,
) : LifecycleViewBindingProperty<F, T>(viewBinder, onViewDestroyed) {

    private var fragmentLifecycleCallbacks: FragmentManager.FragmentLifecycleCallbacks? = null
    private var fragmentManager: FragmentManager? = null

    override fun getValue(thisRef: F, property: KProperty<*>): T {
        val viewBinding = super.getValue(thisRef, property)
        registerFragmentLifecycleCallbacks(thisRef)
        return viewBinding
    }

    private fun registerFragmentLifecycleCallbacks(fragment: Fragment) {
        if (fragmentLifecycleCallbacks != null) {
            return
        }

        fragment.lifecycleScope.launchWhenCreated {
            val fragmentManager = fragment.parentFragmentManager.also { fm ->
                this@FragmentViewBindingProperty.fragmentManager = fm
            }
            fragmentLifecycleCallbacks = ClearOnDestroy().also { callbacks ->
                fragmentManager.registerFragmentLifecycleCallbacks(callbacks, false)
            }
        }
    }

    override fun isViewInitialized(thisRef: F): Boolean {
        if (!viewNeedInitialization) return true

        return if (thisRef !is DialogFragment) {
            thisRef.view != null
        } else {
            super.isViewInitialized(thisRef)
        }
    }

    override fun clear() {
        super.clear()
        fragmentManager?.also { fragmentManager ->
            fragmentLifecycleCallbacks?.let(fragmentManager::unregisterFragmentLifecycleCallbacks)
            this.fragmentManager = null
        }
        fragmentLifecycleCallbacks = null
    }

    override fun getLifecycleOwner(thisRef: F): LifecycleOwner {
        return thisRef.getLifecycleOwner
    }

    private inner class ClearOnDestroy : FragmentManager.FragmentLifecycleCallbacks() {

        override fun onFragmentDestroyed(fm: FragmentManager, f: Fragment) {
            // Fix for destroying view for case with issue of navigation
            postClear()
        }
    }
}